﻿/*=================================================
*FileName:      CResourceManager.cs 
*Author:        lovewaits 
*UnityVersion:  2019.4.34f1 
*Date:          2022-06-25 16:17 
*Description:   
*History:       
=================================================*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using Object = UnityEngine.Object;

public class CResourceManager : MonoSingleton <CResourceManager>
{
    private Dictionary<string, IResource> mLoadingResDic;
    private Dictionary<string, IResource> mLoadedResDic;
    private Dictionary<string, IResource> inDestroyDic;

    private Dictionary<Object, IResource> mResourceCallBackObjDic;//回调来源
    private Dictionary<object, List<IResource>> mResourceRelyObjDic;//引用

    private List<IResource> assistList;
    private bool IsChangingScene;
    private string mLoadingSceneName;
    private AsyncOperationHandle<SceneInstance> mAsync;

    public int disposeTime
    {
        get
        {
#if UNITY_EDITOR||UNITY_ANDROID
            return 1;
#elif UNITY_IOS
            return 0;
#else
            return 1;
#endif
        }
    }

    public int GetLoadingResCount
    {
        get => mLoadingResDic.Count;
    }

    //public PositionChangeReason ChangeSceneReason { get; private set; }

    public void InitData()
    {

        mLoadingResDic = new Dictionary<string, IResource>();
        mLoadedResDic = new Dictionary<string, IResource>();
        inDestroyDic = new Dictionary<string, IResource>();
        mResourceCallBackObjDic = new Dictionary<Object, IResource>();
        mResourceRelyObjDic = new Dictionary<object, List<IResource>>();
        assistList = new List<IResource>();
    }

    private void Update()
    {
        UpdateDestroyQueue();
    }

    void UpdateDestroyQueue()
    {
        if (IsChangingScene || inDestroyDic == null) return;

        var cur = inDestroyDic.GetEnumerator();
        while (cur.MoveNext())
        {
            bool isDestroy = DestroyResource(cur.Current.Key, false, true);
#if UNITY_EDITOR||UNITY_ANDROID
            if (!isDestroy) break;
#endif
            inDestroyDic.Remove(cur.Current.Key);
            break;
        }
    }

    public void AddResSourceObj(object relyObj, Object sourceObj, IResource res)
    {
        if (relyObj != null)
        {
            if (mResourceRelyObjDic.TryGetValue(relyObj, out var _oldRes))
            {
                if (!_oldRes.Contains(res))
                {
                    _oldRes.Add(res);
                }
            }
            else
            {
                mResourceRelyObjDic.Add(relyObj, new List<IResource>() { res });
            }
        }

        if (sourceObj != null)
        {
            if (mResourceCallBackObjDic.TryGetValue(sourceObj, out IResource _oldRes))
            {
                _oldRes.ReleaseCallBack(sourceObj);
                mResourceCallBackObjDic.Remove(sourceObj);
            }
            mResourceCallBackObjDic.Add(sourceObj, res);
        }
    }

    public IResource GetLoadedRes(string path)
    {
        if (mLoadedResDic.ContainsKey(path))
        {
            var tempRes = mLoadedResDic[path];
            if (tempRes.isResValid)
            {
                return tempRes;
            }
            tempRes.Destroy();
            mLoadedResDic.Remove(path);
        }
        return null;
    }
    public IResource GetLoadingRes(string path)
    {
        if (mLoadingResDic.ContainsKey(path))
        {
            var tempRes = mLoadingResDic[path];
            if (tempRes.isResValid)
            {
                return tempRes;
            }
            tempRes.Destroy();
            mLoadingResDic.Remove(path);
        }
        return null;
    }

    //检测资源加载
    //#if UNITY_EDITOR
    //    private void OnGUI()
    //    {
    //        GUIStyle gUIStyle = new GUIStyle();
    //        gUIStyle.normal.textColor = Color.blue;
    //        gUIStyle.fontSize = 30;

    //        GUILayout.Label($"正在加载数量: {mLoadingResDic.Count} 已加载数量: {mLoadedResDic.Count}", gUIStyle);


    //        List<string> LoadingResNameList = new List<string>(mLoadingResDic.Keys);
    //        for (int i = 0; i < LoadingResNameList.Count; i++)
    //        {
    //            string name = LoadingResNameList[i];
    //            var res = mLoadingResDic[name];
    //            GUILayout.Label($"{name}: {res.PercentComplete} {res.IsDone} {res.isResValid}", gUIStyle);
    //        }
    //    }
    //#endif

    /// <summary>
    /// 资源加载
    /// </summary>
    /// <typeparam name="T">加载资源类型</typeparam>
    /// <param name="_resName">资源路径Key</param>
    /// <param name="relyObj">资源加载总依赖的object 例：面板，面板上可以加载多个资源 都依赖于这个面板，当关闭（销毁）面板时去除资源依赖</param>
    /// <param name="sourceObj">资源加载具体依赖 例：同一个Image上只刷新一个Sprite，当前有先后加载情况，保留最后一次加载回调</param>
    /// <param name="onLoadCallBack">加载回调</param>
    /// <returns></returns>
    public CResource<T> AddQueue<T>(string _resName, object relyObj, Object sourceObj = null, CSEventDelegate<CResource<T>>.OnLoaded onLoadCallBack = null)
    {
        if (string.IsNullOrEmpty(_resName))
        {
            return null;
        }
        inDestroyDic.Remove(_resName);
        var res = GetLoadedRes(_resName);
        if (res == null)
        {
            res = GetLoadingRes(_resName);
            if (res == null)
            {
                res = new CResource<T>(_resName);
                mLoadingResDic.Add(_resName, res);
            }
            AddResSourceObj(relyObj, sourceObj, res);
        }
        var typeRes = res as CResource<T>;
        typeRes.AddCallBack(relyObj, sourceObj, onLoadCallBack);
        return typeRes;
    }

    public void RemoveLoadingQueue(IResource res)
    {
        if (mLoadingResDic.ContainsKey(res.ResName))
        {
            mLoadingResDic.Remove(res.ResName);
        }
    }

    public void DestroyAllResourceCallBack()
    {
        using (var cur = mLoadedResDic.GetEnumerator())
        {
            while (cur.MoveNext())
            {
                IResource res = cur.Current.Value;
                if (res != null && res.IsCanBeDelete)
                {
                    res.ReleaseCallBack();
                    res.AddFrontCallBack();
                }
            }
        }

        using (var cur = mLoadingResDic.GetEnumerator())
        {
            while (cur.MoveNext())
            {
                IResource res = cur.Current.Value;
                if (res != null && res.IsCanBeDelete)
                {
                    res.ReleaseCallBack();
                    res.AddFrontCallBack();
                }
            }
        }
    }

    internal void RemoveObjCallBack(Object sourceObj)
    {
        if (sourceObj != null)
        {
            if (mResourceCallBackObjDic.TryGetValue(sourceObj, out IResource _oldRes))
            {
                _oldRes.ReleaseCallBack(sourceObj);
                mResourceCallBackObjDic.Remove(sourceObj);
            }
        }
    }

    internal void RemoveRelyObj(object key)
    {
        if (mResourceRelyObjDic.TryGetValue(key, out var res))
        {
            int count = res.Count;
            for (int i = 0; i < count; i++)
            {
                res[i].RmoveUseRelyObj(key);
            }
            mResourceRelyObjDic.Remove(key);
        }
    }

    public void DestroyAllResource(bool isWithoutChangeScene = false)
    {

        CheckRelyObjDic(isWithoutChangeScene);

        using (var cur = mLoadedResDic.GetEnumerator())
        {
            while (cur.MoveNext())
            {
                var value = cur.Current.Value;
                var key = cur.Current.Key;
                if (Time.time - value.LoadedTime <= disposeTime) continue;

                if (isWithoutChangeScene)//切换场景的时候所有资源都标记为可删除
                {
                    value.IsCanBeDelete = true;
                }

                if (IsCanDeleteChangeScene(value))//如果一个资源下载失败，就算他被标记成不可被删除在切换场景的时候也将这个引用删除，让其在用到的时候在下载
                {
                    if (isWithoutChangeScene && value.isResValid)//加载完成后就释放了
                    {
                        if (!inDestroyDic.ContainsKey(key))
                        {
                            inDestroyDic.Add(key, value);
                        }
                        value.ReleaseCallBack();
                    }
                    else
                    {
                        DestroyResource(key, false, false);
                        assistList.Add(value);
                    }
                }
            }
        }

        for (int i = 0; i < assistList.Count; i++)
        {
            if (mLoadedResDic.ContainsKey(assistList[i].ResName))
            {
                mLoadedResDic.Remove(assistList[i].ResName);
            }
        }
        assistList.Clear();

        using (var cur = mLoadingResDic.GetEnumerator())
        {
            while (cur.MoveNext())
            {
                IResource res = cur.Current.Value;
                if (res != null && res.IsCanBeDelete)
                {
                    res.ReleaseCallBack();
                    res.AddFrontCallBack();
                }
            }
        }
    }

    public void CheckRelyObjDic(bool isWithoutChangeScene)
    {
        if (isWithoutChangeScene)
        {
            mResourceRelyObjDic.Clear();
            return;
        }
        var newDic = new Dictionary<object, List<IResource>>();
        if (mResourceRelyObjDic != null)
        {
            var dic = mResourceRelyObjDic.GetEnumerator();
            while (dic.MoveNext())
            {
                if (dic.Current.Key != null && (!(dic.Current.Key is Object) || (dic.Current.Key as Object) != null))
                {
                    newDic.Add(dic.Current.Key, dic.Current.Value);
                }
            }
            mResourceRelyObjDic.Clear();
            mResourceRelyObjDic = newDic;
        }
    }

    bool IsCanDeleteChangeScene(IResource res)
    {
        if (res.IsCanBeDelete) return true;
        if (!res.isResValid) return true;
        if (res.RelyCount == 0)
        {
            return true;
        }
        return false;
    }


    /// <summary>
    /// 注意二进制文件使用完，destroy 减少占用堆内存
    /// </summary>
    /// <param name="path"></param>
    /// <param name="isUnLoadUnUsedAssets"></param>
    /// <param name="isRemoveFromDic"></param>
    public bool DestroyResource(string path, bool isUnLoadUnUsedAssets = false, bool isRemoveFromDic = true)
    {
        if (string.IsNullOrEmpty(path)) return false;
        if (mLoadedResDic.ContainsKey(path))
        {
            //xxxx
            IResource res = mLoadedResDic[path] as IResource;
            if (!IsCanDeleteChangeScene(res)) return false;

            RemoveRefRes(res);
            res.Destroy();
        }
        else
        {
            IResource res = GetLoadingRes(path);
            if (res != null)
            {
                if (res.IsCanBeDelete)
                {
                    res.ReleaseCallBack();
                    res.AddFrontCallBack();
                }
            }
        }

        if (isRemoveFromDic)
        {
            mLoadedResDic.Remove(path);
        }
        return true;
    }

    void RemoveRefRes(IResource res)
    {
        if (res == null) return;
        //UnityEngine.Debug.LogError("RemoveRefRes！" + res.ResName);
        res.ReleaseCallBack();
    }

    public void wwwLoaded(IResource res)
    {
        RemoveLoadingQueue(res);
        AddmLoadedResDic(res);
    }


    void AddmLoadedResDic(IResource res)
    {
        if (mLoadedResDic.ContainsKey(res.ResName))
        {
            return;
        }
        mLoadedResDic.Add(res.ResName, res);
    }

    #region Scene 

    public IEnumerator ResourcesUnloadUnusedAssets()
    {
        yield return Resources.UnloadUnusedAssets();
    }
    #endregion
}
